package com.ihr360.sharedservice.exceptionmail.service.impl;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.ihr360.shared.exceptionmail.entity.ExceptionRecord;
import com.ihr360.sharedservice.exceptionmail.service.ExceptionMailService;
import com.ihr360.sharedservice.exceptionmail.service.SystemConfigService;
import com.sun.mail.smtp.SMTPAddressFailedException;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;

/**
 * Created by Eric on 2018/6/7/007.
 */
@Service
public class ExceptionMailServiceImpl implements ExceptionMailService {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionMailServiceImpl.class);

    private final static Map<String, Queue<Long>> sendHistory = Maps.newHashMap();
    final private static String EMAIL_ADDRESS = "noreply@ihr360.com";
    final private static String EMAIL_PASSWORD = "Password-12345";
    final private static String PORT = "25";
    final private static String EMAIL_SMTP_SERVER = "smtp.mxhichina.com";
    final private static Boolean USE_SSL = false;

    @Autowired
    private SystemConfigService systemConfigService;

    private Session emailSession;
    private Transport emailTransport;

    @Override
    public void send(ExceptionRecord record) {
        String key = record.getUrl();
        if (!notifyNeeded(key)) {
            return;
        }
        String toAddress = systemConfigService.getExceptionNotifyEmail();

        if (Strings.isNullOrEmpty(toAddress)) {
            return;
        }

        String subject = "[" + occurrence(key) + "][WebException] Exception was thrown in: " + record.getType();
        StringBuilder contentBuilder = new StringBuilder();
        String lineTemplate = "<div><b>%s：</b>%s</div>";
        contentBuilder.append(String.format(lineTemplate, "Type", record.getType()));
        contentBuilder.append(String.format(lineTemplate, "Message", record.getMessage()));
        contentBuilder.append(String.format(lineTemplate, "URL", record.getUrl()));
        contentBuilder.append(String.format(lineTemplate, "CompanyID", record.getCompanyId()));
        contentBuilder.append(String.format(lineTemplate, "CompanyName", record.getCompanyName()));
        contentBuilder.append(String.format(lineTemplate, "UserID", record.getUserId()));
        contentBuilder.append(String.format(lineTemplate, "SystemVersion", record.getVersion()));
        contentBuilder.append(String.format(lineTemplate, "CommitID", record.getCommitId()));
        contentBuilder.append(String.format(lineTemplate, "CommitTime", record.getCommitTime()));
        contentBuilder.append(String.format(lineTemplate, "ServerIP", record.getServerIp()));
        contentBuilder.append(String.format("<div>%s</div>", wrapEmailBody(record.getStackTrace())));
        toAddress = toAddress.replaceAll(";", ",");
        toAddress = toAddress.replaceAll("；", ",");
        toAddress = toAddress.replaceAll("，", ",");
        sendEmail(EMAIL_ADDRESS, EMAIL_PASSWORD, PORT, EMAIL_SMTP_SERVER, USE_SSL, Lists.newArrayList(toAddress.split(";")),
                contentBuilder.toString(), subject, false, null);
    }

    private void sendEmail(String emailAddress, String emailPassword, String port, String emailSmtpServer, Boolean useSsl, List<String> to, String content, String subject, Boolean sendOneByOne, List<String> attachmentIds) {
        if (CollectionUtils.isEmpty(to)) {
            return;
        }
        List<String> rightMailList = Lists.newArrayList();
        for (String temp : to) {
            try {
                InternetAddress[] toList = InternetAddress.parse(temp);
                if (toList.length > 0) {
                    rightMailList.add(temp);
                }
            } catch (AddressException e) {
                logger.error("Illegal send message email address: {}.", e.getMessage());
            }
        }
        if (rightMailList.isEmpty()) {
            return;
        }
        emailSession = this.handleProperties(new Properties(), useSsl, emailSmtpServer, port);

        try {
            emailTransport = emailSession.getTransport(useSsl ? "smtps" : "smtp");
            emailTransport.connect(emailSmtpServer, emailAddress, emailPassword);
            String mailTot = String.join(",", rightMailList);
            InternetAddress[] toList = InternetAddress.parse(mailTot);
            if (toList.length == 0) {
                return;
            }
            if (sendOneByOne) {
                for (InternetAddress temp : toList) {
                    InternetAddress[] tempList = new InternetAddress[1];
                    tempList[0] = temp;
                    doSendEmail(emailAddress, emailPassword, emailSmtpServer, tempList, subject, content, attachmentIds, 0);
                }
            } else {
                doSendEmail(emailAddress, emailPassword, emailSmtpServer, toList, subject, content, attachmentIds, 0);
            }
        } catch (Exception e) {
            logger.error("common send mail connect error :" + e.getMessage());
        } finally {
            if (emailTransport != null) {
                try {
                    emailTransport.close();
                } catch (MessagingException e) {
                    logger.error("common send mail close transport error :" + e.getMessage());
                }
            }
        }
    }

    private void doSendEmail(String emailAddress, String emailPassword, String emailSmtpServer, InternetAddress[] to, String subject, String content, List<String> attachmentIds, int count) {
        if (count > 0) {
            // 有发送失败时，等待 1<<count 秒之后重试。
            try {
                Thread.sleep(1000 * (1 << count));
            } catch (InterruptedException e) {
                logger.warn("Sleep interrupted: {}", e.getMessage());
            }
        }

        // 发送失败时重试。
        try {
            if (CollectionUtils.isEmpty(attachmentIds)) {
                sendEmail(emailAddress, emailPassword, emailSmtpServer, to, subject, content);
            } else {
                sendEmailAndImage(emailAddress, emailPassword, emailSmtpServer, to, content, subject, attachmentIds);
            }
        } catch (SMTPAddressFailedException e) {
            logger.error("Invalid target address: {}.", emailAddress);
        } catch (Exception e) {
            if (count > 3) {
                logger.error("common Cannot send mail after {} retires, giving up..", count);
            } else {
                logger.error("common Send failed, retrying: {}.", e.getMessage());
                logger.error("common Stack trace: " + Throwables.getStackTraceAsString(e));
                doSendEmail(emailAddress, emailPassword, emailSmtpServer, to, subject, content, attachmentIds, count + 1);
            }
        }
    }

    private void sendEmail(String emailAddress, String emailPassword, String emailSmtpServer, InternetAddress[] to, String subject, String content) throws Exception {
        try {
            InternetAddress fromAddress = new InternetAddress(emailAddress);
            MimeMessage message = new MimeMessage(emailSession);
            message.setFrom(fromAddress);
            message.addRecipients(Message.RecipientType.TO, to);
            message.setSentDate(new Date());
            message.setSubject(MimeUtility.encodeText(subject, Charsets.UTF_8.toString(), "B"));

            MimeMultipart allPart = new MimeMultipart("mixed");
            // 创建邮件的各个 MimeBodyPart 部分
            MimeBodyPart body = createContent(content);
            allPart.addBodyPart(body);
            message.setContent(allPart);
            if (!emailTransport.isConnected()) {
                emailTransport.connect(emailSmtpServer, emailAddress, emailPassword);
            }
            emailTransport.sendMessage(message, message.getAllRecipients());
        } catch (UnsupportedEncodingException e) {
            logger.error("Encoding error: {}.", e.getMessage());
        }
    }

    /**
     * 根据传入的文件路径创建附件并返回
     */
/*    public List<MimeBodyPart> createAttachment(List<String> attachmentIds) throws Exception {
        List<MimeBodyPart> mimeBodyParts = Lists.newArrayList();
        for (String string : attachmentIds) {
            MimeBodyPart attachmentPart = new MimeBodyPart();
            BinaryFile gridFSDBFile = binaryFileService.load(string);
            if (gridFSDBFile != null) {
                ByteArrayDataSource fds = new ByteArrayDataSource(gridFSDBFile.getInputStream(),
                        gridFSDBFile.getContentType() == null ? "application/octet-stream" : gridFSDBFile.getContentType());
                attachmentPart.setDataHandler(new DataHandler(fds));
                attachmentPart.setFileName(MimeUtility.encodeText(gridFSDBFile.getFilename()));
                mimeBodyParts.add(attachmentPart);
            }
        }
        return mimeBodyParts;
    }*/

    /**
     * 根据传入的邮件正文body和文件路径创建图文并茂的正文部分
     */
    public MimeBodyPart createContent(String body)
            throws Exception {
        // 用于保存最终正文部分
        MimeBodyPart contentBody = new MimeBodyPart();
        // 用于组合文本和图片，"related"型的MimeMultipart对象
        MimeMultipart contentMulti = new MimeMultipart("related");
        // 正文的文本部分
        MimeBodyPart textBody = new MimeBodyPart();
        textBody.setContent(body, "text/html;charset=gbk");
        contentMulti.addBodyPart(textBody);

        /*Pattern pattern = Pattern.compile("(\"cid:)([a-zA-Z0-9-]*)(\")");
        Matcher matcher = pattern.matcher(body);
        while (matcher.find()) {
            // 正文的图片部分
            BinaryFile gridFSDBFile = binaryFileService.load(matcher.group(2));
            if (gridFSDBFile != null) {
                MimeBodyPart jpgBody = new MimeBodyPart();
                ByteArrayDataSource fds = new ByteArrayDataSource(gridFSDBFile.getInputStream(),
                        gridFSDBFile.getContentType() == null ? "application/octet-stream" : gridFSDBFile.getContentType());
                jpgBody.setDataHandler(new DataHandler(fds));
                jpgBody.setContentID(matcher.group(2));
                contentMulti.addBodyPart(jpgBody);
            }
        }*/

        // 将上面"related"型的 MimeMultipart 对象作为邮件的正文
        contentBody.setContent(contentMulti);
        return contentBody;
    }

    /**
     * 根据传入的 Seesion 对象创建混合型的 MIME消息
     */
    private void sendEmailAndImage(String emailAddress, String emailPassword, String emailSmtpServer, InternetAddress[] to, String body, String subject, List<String> attachmentIds) throws Exception {
        try {
            InternetAddress fromAddress = new InternetAddress(emailAddress);
            MimeMessage msg = new MimeMessage(emailSession);
            msg.setFrom(fromAddress);
            msg.addRecipients(Message.RecipientType.TO, to);
            msg.setSubject(MimeUtility.encodeText(subject, Charsets.UTF_8.toString(), "B"));

            // 将邮件中各个部分组合到一个"mixed"型的 MimeMultipart 对象
            MimeMultipart allPart = new MimeMultipart("mixed");

            // 创建邮件的各个 MimeBodyPart 部分
            MimeBodyPart content = createContent(body);
            allPart.addBodyPart(content);
            //暂不支持附件
            /*if (attachmentIds != null && !attachmentIds.isEmpty()) {
                List<MimeBodyPart> attachments = createAttachment(attachmentIds);
                for (MimeBodyPart mimeBodyPart : attachments) {
                    allPart.addBodyPart(mimeBodyPart);
                }
            }*/

            // 将上面混合型的 MimeMultipart 对象作为邮件内容并保存
            msg.setContent(allPart);
            msg.saveChanges();
            msg.setSentDate(new Date());

            if (!emailTransport.isConnected()) {
                emailTransport.connect(emailSmtpServer, emailAddress, emailPassword);
            }
            emailTransport.sendMessage(msg, msg.getAllRecipients());
        } catch (UnsupportedEncodingException e) {
            logger.error("common Encoding error: {}.", e.getMessage());
        }
    }


    private Session handleProperties(Properties prop, boolean useSsl, String emailSmtpServer, String port) {
        if (Strings.isNullOrEmpty(port)) {
            port = useSsl ? "465" : "25";
        }
        if (useSsl) {
            prop.setProperty("mail.smtps.auth", "true");
            prop.setProperty("mail.smtps.host", emailSmtpServer);
            prop.setProperty("mail.smtps.port", port);
            prop.setProperty("mail.smtps.ssl.protocols", "TLSv1 TLSv1.1 TLSv1.2");
            prop.setProperty("mail.smtps.ssl.ciphersuites", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_RC4_128_MD5 TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
            prop.setProperty("mail.smtps.connectiontimeout", "30000");
            prop.setProperty("mail.smtps.timeout", "30000");
        } else {
            prop.setProperty("mail.smtp.auth", "true");
            prop.setProperty("mail.smtp.host", emailSmtpServer);
            prop.setProperty("mail.smtp.port", port);
            prop.setProperty("mail.smtp.connectiontimeout", "30000");
            prop.setProperty("mail.smtp.timeout", "30000");
        }
        return Session.getInstance(prop);
    }

    private boolean notifyNeeded(String key) {
        Long now = System.currentTimeMillis();
        if (sendHistory.get(key) == null) {
            sendHistory.put(key, Queues.newLinkedBlockingDeque());
            sendHistory.get(key).add(now);
            return true;
        }

        Queue<Long> historyQueue = sendHistory.get(key);
        // 清理掉一个小时之前的所有记录
        while (!historyQueue.isEmpty() && historyQueue.peek() < now - 1000 * 60 * 60) {
            historyQueue.poll();
        }

        if (historyQueue.size() < 1001) {
            historyQueue.add(now);
        }

        int size = historyQueue.size();
        return size == 1 || size == 5 || size == 10 || size == 20 || size == 50 || size % 100 == 0;
    }

    private Integer occurrence(String key) {
        Queue<Long> historyQueue = sendHistory.get(key);
        return historyQueue == null ? 0 : historyQueue.size();
    }

    private String wrapEmailBody(String body) {
        return body == null ? null : body.replace(" ", "&nbsp;").replace("\n", "</br>");
    }
}
